#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <string.h>
#include <errno.h>

/*==============================================================================
=======                    Type definitions                              =======
==============================================================================*/
union semun {
   int              val;    /* Value for SETVAL */
   struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
   unsigned short  *array;  /* Array for GETALL, SETALL */
   struct seminfo  *__buf;  /* Buffer for IPC_INFO
                               (Linux-specific) */
};

/*==============================================================================
======           Defines & Macros for General Purpos                     =======
==============================================================================*/
#define SEM_FILE   "./semfile"

/*==============================================================================
=======                Local variable                                    =======
==============================================================================*/ 


/*==============================================================================
=======                Function Implement List                           =======
==============================================================================*/ 

static void print_err(char *str){
    perror(str);
    exit(-1);
}

int sem_get(int nsems) {
    int semid = -1;
    /* 确保指定文件路径一定存在(如果不存在就创建文件) */
    int fd = open(SEM_FILE, O_RDONLY | O_CREAT, 0664);
    if (fd < 0) print_err("open fail");

    /* 关闭文件 */
    close(fd);

    /* 根据路径生成key值 */
    key_t key = ftok(SEM_FILE, 'a');
    if (key < 0) print_err("ftok fail");

    /* 根据key创建或获取sem标识符(ID) */
    semid = semget(key, nsems, IPC_CREAT | 0664);
    if (semid < 0) print_err("semget fail");

    return semid;
}

/**
 * 初始化信号量集semid中,编号为semnum的信号量,初值value
 * @param semid 信号量集ID
 * @param semnum 信号量编号
 * @param value 信号量初值
 */
void sem_init(int semid, int semnum, int value) {
    int ret = -1;
    union semun sem_un;

    /* semnum 信号量编号
     * SETVAL 设置信号量初值cmd
     * sem_um 包含初始值
     */
    sem_un.val = value;
    ret = semctl(semid, semnum, SETVAL, sem_un);
    if (ret < 0) print_err("sem init fail"); 
}

void sem_p(int semid, int semnum_buf[], int nsops) {
    int ret = -1;
    struct sembuf sops[nsops];

    for (int i = 0; i < nsops; ++i) {
        sops[i].sem_num = semnum_buf[i]; // 信号量编号
        sops[i].sem_op = -1; // -1 p操作, 不要记成 sem_op -= 1了
        sops[i].sem_flg = SEM_UNDO; // 防止死锁, 进程结束时，内核释放进程没有释放的锁
    }

    ret = semop(semid, sops, nsops);
    if (ret < 0) print_err("semop p fail");
}

void sem_v(int semid, int semnum_buf[], int nsops) {
    int ret = -1;
    struct sembuf sops[nsops];

    for (int i = 0; i < nsops; ++i) {
        sops[i].sem_num = semnum_buf[i];  // 信号量编号
        sops[i].sem_op = 1; // +1 v操作,不要记成 sem_op += 1了
        sops[i].sem_flg = SEM_UNDO; // 防止死锁
    }

    ret = semop(semid, sops, nsops);
    if (ret < 0) print_err("semop v fail");
}

void sem_destroy(int semid) {
    int ret = semctl(semid, 0, IPC_RMID); // 销毁信号量集

    if (ret < 0) {
        perror("semctl destroy fail");
    }

    remove(SEM_FILE);
}

